#include "ParkingSpotManager.hpp"
#include "Pipeline.h"
#include "SQLite3Wrapper.hpp"
#include "db.hpp"
#include "debug.hpp"
#include "decode.hpp"
#include "net/file_utils.hpp"
#include "net/http_server.hpp"
#include "net/io_context.hpp"
#include "park.hpp"
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include <unistd.h>

using namespace std::chrono_literals;
using json = nlohmann::json;
using namespace boost::uuids;

template <typename T>
T getOrDefault(const json &j, const std::string &key, const T &defaultValue) {
    if (j.contains(key)) {
        return j.at(key).get<T>();
    } else {
        return defaultValue;
    }
}

template <typename K, typename V>
V getOrDefault(const std::map<K, V> &map, const K &key, const V &defaultValue) {
    auto it = map.find(key);
    if (it != map.end()) {
        return it->second;
    } else {
        return defaultValue;
    }
}

stop_source recv_timeout_stop = stop_source::make();

std::string make_err(std::string msg) {
    json j = {{"error", msg}};
    return j.dump();
}

pr::PipelinePR prc("model/cascade.xml", "model/HorizonalFinemapping.prototxt",
                   "model/HorizonalFinemapping.caffemodel",
                   "model/Segmentation.prototxt",
                   "model/Segmentation.caffemodel",
                   "model/CharacterRecognization.prototxt",
                   "model/CharacterRecognization.caffemodel",
                   "model/SegmenationFree-Inception.prototxt",
                   "model/SegmenationFree-Inception.caffemodel");

uint64_t clac_cost(uint64_t start, uint64_t end) {
    return 2 + (end - start) / 1000 * 1000 * 5;
}

void server(std::string port) {
    io_context ctx;
    auto server = http_server::make();
    server->get_router().route("/", [](http_server::http_request &request) {
        request.write_response(200, "hello world");
    });
    server->get_router().route(
        "/api/license_plate", [](http_server::http_request &request) {
            if (request.body.empty()) {
                request.write_response(400, make_err("no body"));
                return;
            }
            std::vector<uint8_t> data;
            try {
                data = Base64::decode(request.body);
                cv::Mat image = cv::imdecode(data, cv::IMREAD_COLOR);
                std::vector<pr::PlateInfo> res =
                    prc.RunPiplineAsImage(image, pr::SEGMENTATION_FREE_METHOD);
                json resp;
                for (auto st : res) {
                    resp["plate"] = st.getPlateName();   // 车牌号
                    resp["confidence"] = st.confidence;  // 车牌置信度
                    cv::Rect region = st.getPlateRect(); // 车牌位置
                    resp["region"] = {{"x", region.x},
                                      {"y", region.y},
                                      {"width", region.width},
                                      {"height", region.height}};
                    resp["message"] = "success";
                }
                request.write_response(200, resp.dump());
            } catch (std::exception const &e) {
                request.write_response(400, make_err("no base64 data"));
                return;
            }
        });
    server->get_router().route(
        "/api/drivers", [](http_server::http_request &request) {
            auto id = getOrDefault(request.params, {"id"}, {});
            auto driver_name =
                getOrDefault(request.params, {"driver_name"}, {});
            auto driver_type =
                getOrDefault(request.params, {"driver_type"}, {});
            uint8_t type = driver_type.empty() ? 0 : std::stoi(driver_type);
            try {
                json resp;
                auto drivers =
                    ParkDB::getInstance()->getDrivers(id, driver_name, type);
                resp["drivers"] = drivers;
                resp["count"] = drivers.size();
                request.write_response(200, resp.dump());
            } catch (...) {
                request.write_response(500, make_err("internal error"));
            }
        });
    server->get_router().route(
        "/api/dev/register", [](http_server::http_request &request) {
            try {
                if (request.body.empty()) {
                    request.write_response(400, make_err("no body"));
                    return;
                }
                json data = json::parse(request.body);
                std::string driver_name = data["driver_name"];
                uint8_t driver_type = data["driver_type"];
                json resp;
                auto res = ParkDB::getInstance()->getDrivers("", driver_name,
                                                             driver_type);
                if (res.size() > 0) {
                    resp["driver_id"] = res[0]["id"];
                    resp["message"] = "already exists";
                    request.write_response(200, resp.dump());
                    return;
                }
                std::string driver_id = to_string(random_generator()());
                ParkDB::getInstance()->insertDriver(
                    Driver{.id = driver_id,
                           .driver_name = driver_name,
                           .driver_type =
                               static_cast<Driver::DriverType>(driver_type)});
                resp["driver_id"] = driver_id;
                resp["message"] = "success";
                request.write_response(200, resp.dump());
            } catch (...) {
                request.write_response(500, make_err("invalid body"));
            }
        });
    server->get_router().route(
        "/api/car/enter", [](http_server::http_request &request) {
            //"driver_id": "string",
            //"license_plate_image": "base64_encoded_image"
            try {
                if (request.body.empty()) {
                    request.write_response(400, make_err("no body"));
                    return;
                }
                json data = json::parse(request.body);
                std::string driver_id = data["driver_id"];
                if (ParkDB::getInstance()->getDrivers(driver_id).size() == 0) {
                    request.write_response(400, make_err("driver not exist"));
                    return;
                }
                std::string license_plate_image = data["license_plate_image"];
                auto img = Base64::decode(license_plate_image);
                cv::Mat image = cv::imdecode(img, cv::IMREAD_COLOR);
                std::vector<pr::PlateInfo> res =
                    prc.RunPiplineAsImage(image, pr::SEGMENTATION_FREE_METHOD);
                json resp;
                if (res.size() < 1) {
                    resp["error"] = "no plate detected";
                    request.write_response(400, resp.dump());
                    return;
                }
                for (auto st : res) {
                    resp["plate"] = st.getPlateName();   // 车牌号
                    resp["confidence"] = st.confidence;  // 车牌置信度
                    cv::Rect region = st.getPlateRect(); // 车牌位置
                    resp["region"] = {{"x", region.x},
                                      {"y", region.y},
                                      {"width", region.width},
                                      {"height", region.height}};
                    resp["enter_time"] = time(nullptr);
                    resp["message"] = "success";
                    if (st.confidence < 0.8) {
                        resp["isValid"] = false;
                        debug(), "confidence too low";
                        request.write_response(200, resp.dump());
                        return;
                    }
                    resp["isValid"] = true;
                    auto res = ParkDB::getInstance()->getSpotWithInfo(
                        "", -1, st.getPlateName());
                    if (res.size() != 0) {
                        request.write_response(400, make_err("car exist"));
                        return;
                    }
                    resp["record_id"] = to_string(random_generator()());
                    // 停入随机位置
                    auto spot_id = ParkingSpotManager::getInstance()->getID();
                    resp["spot_id"] = spot_id;
                    ParkDB::getInstance()->insertInfo(ParkInfo{
                        .id = resp["record_id"],
                        .license_plate = st.getPlateName(),
                        .curr_status = ParkInfo::Status::ENTER,
                        .spot_id = spot_id,
                        .enter_time = time(nullptr),
                        .exit_time = 0,
                        .enter_driver = driver_id,
                        .exit_driver = "",
                    });
                    ParkDB::getInstance()->updateSpot({
                        .id = spot_id,
                        .curr_status = Spot::Status::OCCUPIED,
                        .info_id = resp["record_id"],
                    });
                    break;
                }
                debug(), "car enter";
                request.write_response(200, resp.dump());
            } catch (std::exception &e) {
                debug(), "error", e.what();
                request.write_response(500, make_err("bad request"));
            }
        });
    server->get_router().route(
        "/api/car/exit", [](http_server::http_request &request) {
            //"driver_id": "string",
            //"license_plate_image": "base64_encoded_image"
            try {
                if (request.body.empty()) {
                    request.write_response(400, make_err("no body"));
                    return;
                }
                json data = json::parse(request.body);
                if (!data.contains("driver_id") ||
                    !data.contains("license_plate_image")) {
                    request.write_response(401, make_err("bad request"));
                    return;
                }
                std::string driver_id = data["driver_id"];
                if (ParkDB::getInstance()->getDrivers(driver_id).size() == 0) {
                    request.write_response(400, make_err("driver not exist"));
                    return;
                }
                std::string license_plate_image = data["license_plate_image"];
                auto img = Base64::decode(license_plate_image);
                cv::Mat image = cv::imdecode(img, cv::IMREAD_COLOR);
                std::vector<pr::PlateInfo> res =
                    prc.RunPiplineAsImage(image, pr::SEGMENTATION_FREE_METHOD);
                json resp;
                if (res.size() < 1) {
                    resp["error"] = "no plate detected";
                    request.write_response(400, resp.dump());
                    return;
                }
                for (auto st : res) {
                    resp["plate"] = st.getPlateName();   // 车牌号
                    resp["confidence"] = st.confidence;  // 车牌置信度
                    cv::Rect region = st.getPlateRect(); // 车牌位置
                    resp["region"] = {{"x", region.x},
                                      {"y", region.y},
                                      {"width", region.width},
                                      {"height", region.height}};
                    auto now = time(nullptr);
                    resp["exit_time"] = now;
                    resp["message"] = "success";
                    if (st.confidence < 0.8) {
                        resp["isValid"] = false;
                        break;
                    }
                    // 获取该车牌号的停车信息
                    auto res = ParkDB::getInstance()->getSpotWithInfo(
                        "", -1, st.getPlateName());
                    if (res.size() == 0) {
                        request.write_response(400, make_err("car not exist"));
                        return;
                    }
                    resp["isValid"] = true;
                    auto info = res[0];
                    resp["spot_id"] = info["spot_id"];
                    uint64_t cost = clac_cost(info["enter_time"], now);
                    debug(), "car", st.getPlateName(), "exit", cost;
                    // 更新停车信息
                    ParkDB::getInstance()->updateInfo(ParkInfo{
                        .id = info["info_id"],
                        .license_plate = st.getPlateName(),
                        .curr_status = ParkInfo::Status::EXIT,
                        .spot_id = info["spot_id"],
                        .enter_time = info["enter_time"],
                        .exit_time = now,
                        .enter_driver = info["enter_driver"],
                        .exit_driver = driver_id,
                        .cost = cost,
                    });
                    // 更新停车位状态
                    ParkDB::getInstance()->updateSpot(Spot{
                        .id = info["spot_id"],
                        .curr_status = Spot::Status::AVAILABLE,
                        .info_id = "",
                    });
                    break;
                }
                debug(), "exit success";
                request.write_response(200, resp.dump());
            } catch (std::exception &e) {
                debug(), "error", e.what();
                request.write_response(500, make_err("bad request"));
            }
        });
    server->get_router().route(
        "/api/records", [](http_server::http_request &request) {
            auto id = getOrDefault(request.params, {"id"}, {});
            auto plate = getOrDefault(request.params, {"license_plate"}, {});
            auto curr_status = getOrDefault(request.params, {"status"}, {});
            auto enter_time =
                getOrDefault(request.params, {"enter_time"}, {"0"});
            auto exit_time = getOrDefault(request.params, {"exit_time"}, {"0"});
            auto enter_driver =
                getOrDefault(request.params, {"enter_driver"}, {});
            auto exit_driver =
                getOrDefault(request.params, {"exit_driver"}, {});
            auto spot_id = getOrDefault(request.params, {"spot_id"}, {});
            int status = curr_status.empty() ? -1 : std::stoi(curr_status);
            try {
                json resp;
                auto records = ParkDB::getInstance()->getInfo(
                    id, plate, status, spot_id, enter_driver,
                    std::stoi(enter_time), exit_driver, std::stoi(exit_time));
                resp["records"] = records;
                resp["count"] = records.size();
                request.write_response(200, resp.dump());
            } catch (std::invalid_argument const &e) { // stoi错误
                request.write_response(401, make_err("not valid number"));
            } catch (std::exception &e) {
                debug(), "error", e.what();
                request.write_response(500, make_err("bad request"));
            }
        });
    server->get_router().route(
        "/api/parking_spots", [](http_server::http_request &request) {
            auto plate = getOrDefault(request.params, {"spot_id"}, {});
            auto curr_status = getOrDefault(request.params, {"status"}, {});
            auto curr_car_plate =
                getOrDefault(request.params, {"license_plate"}, {});
            int status = curr_status.empty() ? -1 : std::stoi(curr_status);
            try {
                json resp;
                auto spots = ParkDB::getInstance()->getSpotWithInfo(
                    plate, status, curr_car_plate);
                resp["spots"] = spots;
                resp["count"] = spots.size();
                request.write_response(200, resp.dump());
            } catch (std::exception &e) {
                debug(), "error", e.what();
                request.write_response(500, make_err("bad request"));
            }
        });
    server->get_router().route(
        // 停车
        "/api/car/spot", [](http_server::http_request &request) {
            try {
                auto data = json::parse(request.body);
                std::string spot_id = data["spot_id"];
                std::string license_plate_image = data["license_plate_image"];
                auto img = Base64::decode(license_plate_image);
                cv::Mat image = cv::imdecode(img, cv::IMREAD_COLOR);
                std::vector<pr::PlateInfo> res =
                    prc.RunPiplineAsImage(image, pr::SEGMENTATION_FREE_METHOD);
                json resp;
                if (res.size() < 1) {
                    request.write_response(401, make_err("no plate detected"));
                    return;
                }
                if (ParkDB::getInstance()->getSpotWithInfo(spot_id).size() ==
                    0) {
                    request.write_response(401, make_err("invalid spot id"));
                    return;
                }
                for (auto st : res) {
                    resp["plate"] = st.getPlateName();   // 车牌号
                    resp["confidence"] = st.confidence;  // 车牌置信度
                    cv::Rect region = st.getPlateRect(); // 车牌位置
                    resp["region"] = {{"x", region.x},
                                      {"y", region.y},
                                      {"width", region.width},
                                      {"height", region.height}};
                    resp["enter_time"] = time(nullptr);
                    resp["message"] = "success";
                    if (st.confidence < 0.8) {
                        resp["isValid"] = false;
                    } else {
                        resp["isValid"] = true;
                        // 获取该车牌号的停车信息
                        auto res = ParkDB::getInstance()->getInfo(
                            "", st.getPlateName());
                        if (res.size() == 0) {
                            request.write_response(401,
                                                   make_err("no parking info"));
                            return;
                        } else {
                            // 成功识别到车牌号，更新停车信息和车位信息
                            auto info = res[0];
                            ParkDB::getInstance()->updateInfo(ParkInfo{
                                .id = info["id"],
                                .license_plate = st.getPlateName(),
                                .curr_status = ParkInfo::Status::SPOT,
                                .spot_id = spot_id,
                                .enter_time = info["enter_time"],
                                .exit_time = 0,
                                .enter_driver = info["enter_driver"],
                                .exit_driver = "",
                            });
                            ParkDB::getInstance()->updateSpot(Spot{
                                .id = spot_id,
                                .curr_status = Spot::Status::OCCUPIED,
                                .info_id = info["id"],
                            });
                        }
                    }
                    request.write_response(200, resp.dump());
                    return;
                }
            } catch (std::exception &e) {
                debug(), "error", e.what();
                request.write_response(500, make_err("bad request"));
            }
        });
    server->get_router().route(
        "/api/allrecords", [](http_server::http_request &request) {
            try {
                json resp;
                auto spots = ParkDB::getInstance()->getAllInfo();
                resp["records"] = spots;
                resp["count"] = spots.size();
                request.write_response(200, resp.dump());
            } catch (std::exception &e) {
                debug(), "error", e.what();
                request.write_response(500, make_err("bad request"));
            }
        });
    server->do_start("0.0.0.0", port);
    ctx.join();
}

int main(int argc, char **argv) {
    if (argc != 2) {
        fmt::println("usage: {} <port>", argv[0]);
        return 1;
    }
    std::string port = argv[1];
    try {
        ParkDB *db = ParkDB::getInstance();
        debug(), "db initialized successfully", db;
        auto res = db->getAllSpot();
        debug(), res.dump();
        debug(), "start server";
        server(port);
    } catch (std::system_error const &e) {
        fmt::println("{} ({}/{})", e.what(), e.code().category().name(),
                     e.code().value());
    }
    return 0;
}
